﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Windows;
using MediaAssistant.Controls.WaitScreen;
using MediaAssistant.Data;
using MefBasic.Data;
using MefBasic.Helper;
using MefBasic.Threading;
using MediaAssistant.DAL;

namespace MediaAssistant.MusicDJ
{
    public abstract class AMusicDJ : DependencyNotifyObject, IMusicDJ
    {
        public static readonly DependencyProperty CanMoveNextProperty =
            DependencyProperty.Register("CanMoveNext", typeof (bool), typeof (AMusicDJ), new UIPropertyMetadata(false));

        public static readonly DependencyProperty CanMovePreviousProperty =
            DependencyProperty.Register("CanMovePrevious", typeof (bool), typeof (AMusicDJ),
                                        new UIPropertyMetadata(false));


        [Import]
        protected Lazy<WaitScreenPresenter> WaitScreenPresenter { get; set; }

        protected AMusicDJ(string name)
        {
            Name = name;
            Random = new Random();
            PreviousMusicStack = new Stack<Music>();
        }

        #region Implementation of IMusicDJ

        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof (string), typeof (AMusicDJ), new UIPropertyMetadata(null));

        public Music InitializedMusic { get; private set; }

        public List<Music> NextMusics { get; set; }

        public string Name
        {
            get { return (string) GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }

        public void Next()
        {
            var job = new Job(j =>
                                  {
                                      var playingMusic = GetNextMusic(j.Store.GetObject<Music>());
                                      j.Store.Remove<Music>();
                                      j.Store.Add(playingMusic);
                          
                                  });
            job.Store.Add(DataSource.PlayingMusic);
            job.StartBlocked();
            DataSource.PlayingMusic = job.Store.GetObject<Music>();
        }

        public void Previous()
        {
            DataSource.PlayingMusic = GetPreviousMusic();
        }

        public virtual void Initialize(Music playingMusic)
        {
            InitializedMusic = playingMusic;
            AllMusics = GetMusics();
            NextMusics = AllMusics.ToList();
            UpdateMusicStack();
            SynchronizationContextHelper.Instance.Send(d => ExcludeMusic(playingMusic), null);
            
            PrepareForNextMusic(playingMusic);
        }

        protected virtual void UpdateMusicStack()
        {
            
        }

        public List<Music> AllMusics { get; set; }

        public LibraryDataSource DataSource { get; set; }

        private Music GetNextMusic(Music playingMusic)
        {
            var randomNumber = Random.NextDouble();
            Music nextMusic=null;
            foreach (var music in NextMusics)
            {
                nextMusic = music;
                if (music.CumulativeProbability > randomNumber)
                {
                    break;
                }
            }
            if (playingMusic != null)
            {
                PreviousMusicStack.Push(playingMusic);
                
            }
            if(nextMusic!=null)
            {
                ExcludeMusic(nextMusic);
                PrepareForNextMusic(nextMusic);
            }
            return nextMusic;
        }

        protected List<Music> GetMusics()
        {
            var playingPlaylist = DatabaseManager.Instance.GetNowPlayingPlaylist();
            return playingPlaylist.Musics.Where(m=>File.Exists(m.FullPath)).OrderBy(o => o.Index).ToList();
        }

        private Music GetPreviousMusic()
        {
            NextMusics.Insert(0,DataSource.PlayingMusic);
            var previousMusic = PreviousMusicStack.Pop();
            PrepareForNextMusic(DataSource.PlayingMusic);
            return previousMusic;
        }

        #endregion

        #region IMusicDJ Members

        public bool CanMoveNext
        {
            get { return (bool) GetValue(CanMoveNextProperty); }
            set { SetValue(CanMoveNextProperty, value); }
        }

        public bool CanMovePrevious
        {
            get { return (bool) GetValue(CanMovePreviousProperty); }
            set { SetValue(CanMovePreviousProperty, value); }
        }

        protected Random Random { get; set; }
        protected Stack<Music> PreviousMusicStack { get; set; }

        #endregion

        protected double CalculateCumulativeProbability()
        {
            var cumulativeProbability = 0d;
            foreach (var music in NextMusics)
            {
                cumulativeProbability += music.Probability;
                music.CumulativeProbability = cumulativeProbability;
            }
            return cumulativeProbability;
        }

        protected void ExcludeMusic(Music music)
        {
            NextMusics.Remove(music);
            music.Probability = 0;
            music.CumulativeProbability = 0;
        }

        protected abstract void CalculateProbability(Music playingMusic);

        protected void PrepareForNextMusic(Music playingMusic)
        {
            if (NextMusics.Count > 0)
            {
                CalculateProbability(playingMusic);
            }
            SetMoveNext(CalculateCumulativeProbability());
            SetMovePrevious();
        }

        public void SetMovePrevious()
        {
            SynchronizationContextHelper.Instance.Send(s => CanMovePrevious = (int) s > 0, PreviousMusicStack.Count);
        }

        public void SetMoveNext(double calculateCumulativeProbability)
        {
            SynchronizationContextHelper.Instance.Send(s => CanMoveNext = (double) s > 0, calculateCumulativeProbability);
        }
    }
}